package config

import (
	"fmt"
	"os"
	"strings"

	"gopkg.in/yaml.v3"

	"github.com/riete/errors"
)

type Mode string

const (
	Include Mode = "include"
	Exclude Mode = "exclude"
	Default Mode = ""
)

var modeCheck = map[Mode]bool{Include: true, Exclude: true, Default: true}

var Config = new(Migration)

type ColumnMapping struct {
	Source []string `yaml:"source,flow"`
	Target []string `yaml:"target,flow"`
}

func (c ColumnMapping) Validate() errors.Error {
	if len(c.Source) != len(c.Target) {
		return errors.New("column 映射 source 和 target长度不一致")
	}
	return nil
}

type Table struct {
	Source        string        `yaml:"source"`
	Target        string        `yaml:"target"`
	Condition     string        `yaml:"condition,omitempty"`
	Distributed   bool          `yaml:"distributed"`
	ColumnMapping ColumnMapping `yaml:"columnMapping,omitempty"`
}

func (t Table) SourceColumn() string {
	if len(t.ColumnMapping.Source) > 0 {
		return fmt.Sprintf("%s", strings.Join(t.ColumnMapping.Source, ","))
	}
	return "*"
}

func (t Table) TargetColumn() string {
	if len(t.ColumnMapping.Target) > 0 {
		return fmt.Sprintf("(%s)", strings.Join(t.ColumnMapping.Target, ","))
	}
	return ""
}

func (t Table) Validate() errors.Error {
	if err := t.ColumnMapping.Validate(); err != nil {
		return err.Trace(fmt.Sprintf("table [%s] column 映射配置校验失败", t.Source))
	}
	return nil
}

type Database struct {
	Source string  `yaml:"source"`
	Target string  `yaml:"target"`
	Tables []Table `yaml:"tables"`
}

type ClickHouse struct {
	Host        string `yaml:"host"`
	Port        int    `yaml:"port"`
	Username    string `yaml:"username"`
	Password    string `yaml:"password"`
	ClusterName string `yaml:"clusterName,omitempty"`
}

type DatabaseFilter struct {
	DbName          string   `yaml:"dbName"`
	TableAll        bool     `yaml:"tableAll"`
	TableFilter     []string `yaml:"tableFilter,flow"`
	TableFilterMode Mode     `yaml:"tableFilterMode"`
	matcher         map[string]bool
}

func (d *DatabaseFilter) Match(tableName string) bool {
	if d.matcher == nil {
		d.matcher = make(map[string]bool)
		for _, f := range d.TableFilter {
			d.matcher[f] = true
		}
	}
	if d.TableAll && d.TableFilterMode == Exclude {
		return !d.matcher[tableName]
	}
	if d.TableFilterMode == Include {
		if d.TableAll {
			return true
		}
		return d.matcher[tableName]
	}
	return true
}

type Migration struct {
	Source          ClickHouse       `yaml:"source"`
	Target          ClickHouse       `yaml:"target"`
	BatchSize       uint64           `yaml:"batchSize"`
	Concurrency     int              `yaml:"concurrency"` // 将QPS改为Concurrency
	MigrationConfig []DatabaseFilter `yaml:"migrationConfig"`
	DatabaseConfig  []Database       `yaml:"databaseConfig"`
	tbMatcher       map[string]DatabaseFilter
	dbMatcher       map[string]bool
}

func (m *Migration) Match(dbname, tableName string) bool {
	if len(m.MigrationConfig) == 0 {
		return true
	}
	if m.tbMatcher == nil {
		m.tbMatcher = make(map[string]DatabaseFilter)
		m.dbMatcher = make(map[string]bool)
		for _, d := range m.MigrationConfig {
			m.tbMatcher[d.DbName] = d
			m.dbMatcher[d.DbName] = true
		}
	}
	tbMatcher := m.tbMatcher[dbname]
	return m.dbMatcher[dbname] && tbMatcher.Match(tableName)
}

func (m Migration) Validate() errors.Error {
	if m.BatchSize < 0 {
		return errors.New("batchSize 需要 >= 0")
	}
	for _, mc := range m.MigrationConfig {
		if !modeCheck[mc.TableFilterMode] {
			return errors.New("tableFilterMode 匹配模式只能是 include 或 exclude")
		}
	}
	for _, d := range m.DatabaseConfig {
		for _, t := range d.Tables {
			if err := t.Validate(); err != nil {
				return err.Trace("数据迁移配置校验失败")
			}
		}
	}
	return nil
}

func LoadConfig(path string) errors.Error {
	d, err := os.ReadFile(path)
	if err != nil {
		return errors.NewFromErr(err)
	}
	if err = yaml.Unmarshal(d, Config); err != nil {
		return errors.NewFromErr(err)
	}
	// 为了向后兼容，如果Concurrency没有设置但QPS设置了，使用QPS的值
	// 注意：这里需要在yaml.Unmarshal之后手动处理，因为字段名已更改
	if Config.Concurrency <= 0 {
		Config.Concurrency = 1
	}
	return Config.Validate()
}

func WriteConfig(path string, config *Migration) errors.Error {
	data, err := yaml.Marshal(config)
	if err != nil {
		return errors.NewFromErr(err)
	}
	return errors.NewFromErr(os.WriteFile(path, data, 0644))
}